#include <QDebug>
#include <QDesktopWidget>
#include <QX11Info>

#include "dexwm.h"
#include "dexwin.h"

DexWM::DexWM(int &argc, char **argv) : QApplication(argc, argv)
	{ resolveAtoms(); }

DexWM::~DexWM()
	{
		foreach (QFrame *win, clients)
			win->deleteLater();
	}

Atom DexWM::getWindowType(Window w)
	{
		Atom windowType = None;
		Atom actualType;
		int format;
		unsigned long nitems, bytes_after;
		unsigned char *prop_return = NULL;

		if(XGetWindowProperty(QX11Info::display(),
							  w,
							  d_NET_WM_WINDOW_TYPE,
							  0L,
							  sizeof(Atom),
							  False,
							  XA_ATOM,
							  &actualType,
							  &format,
							  &nitems,
							  &bytes_after,
							  &prop_return) == Success)
		{
			windowType = *(Atom *)prop_return;
			XFree(prop_return);
		}

		return windowType;
	}

bool DexWM::isWinAlreadyMapped(Window w)
	{
		if (clients.contains(w))
		{
			if (clients.value(w) != NULL)
				return true;
			else
				return false;
		}
		return false;
	}

void DexWM::handleConfigureRequest(Window w)
	{
		if (clients.contains(w))
				qDebug() << "ConfigureRequest:" << w;
	}

void DexWM::handleDestroyNotify(Window w)
	{ handleUnmapNotify(w); }

void DexWM::handleMapRequest(Window w)
	{
		// If client is not already mapped,
		//	then determine what kind of window it is
		//	and add a window frame if necessary.
		if (!isWinAlreadyMapped(w))
		{
			qDebug() << "Mapping requested by client #"
					<< w;
			Atom type = getWindowType(w);
			if (type == d_NET_WM_WINDOW_TYPE_DESKTOP ||
				type == d_NET_WM_WINDOW_TYPE_DOCK)
			{
				XMapWindow(QX11Info::display(), w);
				clients.insert(w, 0);
				return;
			}

			clients.insert(w, new DexWin(w));
		}

		else qDebug() << "Client #"
					  << w
					  << " already exists. Nothing to be done here.";
	}

void DexWM::handleUnmapNotify(Window w /*, bool preservePlace*/)
	{
		if (clients.contains(w))
		{
			qDebug() << "Unmapping" << w;
			if (clients.value(w) != NULL)
				clients.take(w)->deleteLater();
			else
				clients.take(w);
//			(preservePlace) ? qDebug() << "... but preserving": qDebug() << "... and not preserving";
		}
	}

void DexWM::resolveAtoms()
	{
		// ICCCM
		d_WM_PROTOCOLS = XInternAtom(QX11Info::display(), "WM_PROTOCOLS", false);
		d_WM_DELETE_WINDOW = XInternAtom(QX11Info::display(), "WM_DELETE_WINDOW", false);
		d_WM_CHANGE_STATE = XInternAtom(QX11Info::display(), "WM_CHANGE_STATE", false);
		d_WM_STATE = XInternAtom(QX11Info::display(), "WM_STATE", false);
		d_WM_TAKE_FOCUS = XInternAtom(QX11Info::display(), "WM_TAKE_FOCU", false);
//		XA_RESOURCE_MANAGER
//		XA_COLORMAP
//		XA_WM_HINTS
//		XA_WM_NORMAL_HINTS
//		XA_WM_NAME
//		XA_WM_TRANSIENT_FOR
		// EWMH
		d_NET_WM_NAME = XInternAtom(QX11Info::display(), "_NET_WM_NAME", false);
		d_NET_WM_ICON = XInternAtom(QX11Info::display(), "_NET_WM_ICON", false);
		d_NET_SUPPORTED = XInternAtom(QX11Info::display(), "_NET_SUPPORTED", false);
		d_NET_WM_USER_TIME = XInternAtom(QX11Info::display(), "_NET_WM_USER_TIME", false);
		d_NET_WM_STATE = XInternAtom(QX11Info::display(), "_NET_WM_STATE", false);
		d_NET_WM_WINDOW_TYPE = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE", false);
		d_NET_WM_WINDOW_TYPE_NORMAL = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_NORMAL", false);
		d_NET_WM_WINDOW_TYPE_DESKTOP = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_DESKTOP", false);
		d_NET_WM_WINDOW_TYPE_DIALOG = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_DIALOG", false);
		d_NET_WM_WINDOW_TYPE_SPLASH = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_SPLASH", false);
		d_NET_WM_WINDOW_TYPE_DOCK = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_DOCK", false);
		d_NET_WM_WINDOW_TYPE_TOOLBAR = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_TOOLBAR", false);
		d_KDE_NET_WM_SYSTEM_TRAY_WINDOW = XInternAtom(QX11Info::display(), "_KDE_NET_WM_SYSTEM_TRAY_WINDOW", false);
		d_NET_WM_STRUT = XInternAtom(QX11Info::display(), "_NET_WM_STRUT", false);
		d_NET_WM_STRUT_PARTIAL = XInternAtom(QX11Info::display(), "_NET_WM_STRUT_PARTIAL", false);
	}

bool DexWM::x11EventFilter(XEvent *event)
{
//	qDebug() << event->type;
	switch (event->type)
	{
		/*	MapRequest:
		 *	Client requesting to show itself. */
		case MapRequest:
			handleMapRequest(event->xmaprequest.window);
			break;

		/*	UnmapNotify:
		 *	Window, pop-up, menu, what-have-you, has disappeared. */
		case UnmapNotify:
			handleUnmapNotify(event->xunmap.window);
			break;

		/*	DestroyNotify:
		 *	The client has been killed (externally). */
		case DestroyNotify:
			handleDestroyNotify(event->xdestroywindow.window);
			break;

		/*	ConfigureRequest:
		 *	The client is trying to move, resize, etc. */
		case ConfigureRequest:
			handleConfigureRequest(event->xconfigurerequest.window);
			break;

		case ConfigureNotify:
			if (clients.contains(event->xconfigure.window))
				qDebug() << "ConfigureNotify:" << event->xconfigure.window;
			break;

		case PropertyNotify:
			if (isWinAlreadyMapped(event->xproperty.window))
			{
				qDebug() << "PropertyNotify:"
						 << XGetAtomName(QX11Info::display(), event->xproperty.atom)
						 << event->xproperty.window;
				// Client requesting a name change.
				if (event->xproperty.atom == XA_WM_NAME ||
					event->xproperty.atom == d_NET_WM_NAME)
					clients.value(event->xproperty.window)->updateWMName();
				// Client seems to have received a click from mouse.
				if (event->xproperty.atom == d_NET_WM_USER_TIME)
					clients.value(event->xproperty.window)->raise();
			}
			break;

		/*	ClientMessage:
		 *	Client to server (or client) communication. */
		case ClientMessage:
			if (clients.contains(event->xclient.window))
				qDebug() << "ClientMessage:"
						 << XGetAtomName(QX11Info::display(), event->xclient.message_type)
						 << event->xclient.window;
			/*	If the client is not a regular window (_NET_WM_WINDOW_TYPE_NORMAL),
			 *	 a dialog (_NET_WM_WINDOW_TYPE_DIALOG),
			 *	 or a utility (_NET_WM_WINDOW_TYPE_UTILITY),
			 *	 then unparent it and delete its frame. */
			break;
	}

	return false;
}
